package com.hyzh.latte.gather;

import java.util.*;

public class GatherUtils {
    //半徑 单位米
    private static final Double RADIUS = 3d;
    //人數 最小人数
    private static final Integer NUM = 3;

    public static Set<PersonLocation> detectGatheringPoint(List<PersonLocation> locations) {
        System.out.println("人数" + locations.size());
        long l = System.currentTimeMillis();
        init(locations);
        Set<PersonLocation> personLocations = new HashSet<>();
        Map<Long, List<PersonLocation>> map = calculateNeighbors(locations, RADIUS);
        List<List<PersonLocation>> clusters = dbscan(locations, NUM, map);
        map.clear();
        int i = 1;
        for (List<PersonLocation> cluster : clusters) {
            PersonLocation center = calculateCenter(cluster);
            if (center.getLon() > 0) {
                center.setPersonNum(cluster.size());
                personLocations.add(center);
                System.out.println("聚集点" + i + "的中心经度: " + center.getLon() + ", 纬度: " + center.getLat() + ", 人数: " + cluster.size());
                int p = 1;
                for (PersonLocation personLocation : cluster) {
                    System.out.println(personLocation.getLat() + "  " + personLocation.getLon());
                    double v = calculateDistance(center.getLat(), center.getLon(), personLocation.getLat(), personLocation.getLon());
                    System.out.println("中心点距离" + p + "点" + "的距离是" + v + "米");
                    p++;
                }
            }
            i++;
        }
        System.out.println("执行时间" + (System.currentTimeMillis() - l));
        return personLocations;
    }

    public static Map<Long, List<PersonLocation>> calculateNeighbors(List<PersonLocation> points, double epsilon) {
        Map<Long, List<PersonLocation>> neighborsMap = new HashMap<>();
        for (PersonLocation point : points) {
            List<PersonLocation> neighbors = new ArrayList<>();
            for (PersonLocation other : points) {//考虑优化 过滤掉经纬度明显差异过大的数据
                if (calculateDistance(point.getLat(), point.getLon(), other.getLat(), other.getLon()) <= epsilon) {
                    neighbors.add(other);
                }
            }
            neighborsMap.put(point.getId(), neighbors);
        }
        return neighborsMap;
    }

    private static void init(List<PersonLocation> locations) {
        long i = 1;
        for (PersonLocation location : locations) {
            location.setId(i);
            i++;
        }
    }

    //计算聚集点
    public static List<List<PersonLocation>> dbscan(List<PersonLocation> points, int minPoints, Map<Long, List<PersonLocation>> map) {
        List<List<PersonLocation>> clusters = new ArrayList<>();//
        Set<Long> visited = new HashSet<>();
        for (PersonLocation point : points) {
            if (visited.contains(point.getId())) {//已访问
                continue;
            }
            visited.add(point.getId());
            List<PersonLocation> neighbors = map.get(point.getId());
            if (neighbors.size() < minPoints) {//大于最小聚集点数 即为核心点
                continue;
            }
            //由核心点出发
            List<PersonLocation> cluster = new ArrayList<>(neighbors);//密度可达点均为簇
            expandCluster(neighbors, cluster, visited, map, minPoints);
            clusters.add(cluster);
        }
        visited.clear();
        return clusters;
    }

    //领域不可达 neighbors取完 则簇确定
    public static void expandCluster(List<PersonLocation> neighbors, List<PersonLocation> cluster,
                                     Set<Long> visited, Map<Long, List<PersonLocation>> map, int minPoints) {
        Set<PersonLocation> toAdd = new HashSet<>(); // 用于暂存需要添加的元素
        int index = 0;
        while (index < neighbors.size()) {
            PersonLocation neighbor = neighbors.get(index);
            if (!visited.contains(neighbor.getId())) { // 邻居点未被访问过
                visited.add(neighbor.getId());
                List<PersonLocation> neighborNeighbors = map.get(neighbor.getId());
                if (neighborNeighbors.size() >= minPoints) {
                    for (PersonLocation neighborNeighbor : neighborNeighbors) {
                        if (!neighbors.contains(neighborNeighbor)) {
                            toAdd.add(neighborNeighbor);
                        }
                    }
                }
            }
            if (!cluster.contains(neighbor)) {
                cluster.add(neighbor);
            }
            index++;
            if (index == neighbors.size() && !toAdd.isEmpty()) {
                neighbors.addAll(toAdd);
                toAdd.clear();
                index = 0;
            }
        }
    }

    //计算两个经纬度之间的距离
    public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
        int R = 6371; // 地球半径，单位为千米
        double dLat = Math.toRadians(lat2 - lat1);
        double dLon = Math.toRadians(lon2 - lon1);
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
                        Math.sin(dLon / 2) * Math.sin(dLon / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return R * c * 1000;
    }

    //获取聚集点的中心点
    private static PersonLocation calculateCenter(List<PersonLocation> locations) {
        double sumLat = 0.0;
        double sumLon = 0.0;
        for (PersonLocation location : locations) {
            sumLat += location.getLat();
            sumLon += location.getLon();
        }
        return new PersonLocation(sumLat / locations.size(), sumLon / locations.size());
    }

    //测试
    public static void main(String[] args) {
        List<PersonLocation> origin = new ArrayList<>();
        origin.add(new PersonLocation(32.220676, 106.187691));
        origin.add(new PersonLocation(32.220249, 106.182442));
        origin.add(new PersonLocation(32.214344, 106.179268));
        origin.add(new PersonLocation(32.217567, 106.181770));
        origin.add(new PersonLocation(32.22068, 106.187668));
        origin.add(new PersonLocation(32.220249, 106.182449));
        origin.add(new PersonLocation(32.21434, 106.179253));
        origin.add(new PersonLocation(32.217571, 106.181801));
        origin.add(new PersonLocation(32.220676, 106.187691));
        origin.add(new PersonLocation(32.220249, 106.182449));
        origin.add(new PersonLocation(32.214344, 106.179268));
        origin.add(new PersonLocation(32.217587, 106.181808));
        origin.add(new PersonLocation(32.220669, 106.187729));
        origin.add(new PersonLocation(32.220249, 106.182442));
        origin.add(new PersonLocation(32.214359, 106.179298));
        origin.add(new PersonLocation(32.217598, 106.181831));
        double a = 39.908695547541825;
        double b = 116.3975;
        for (int i = 0; i < 10; i++) {
            double[] doubles = generateRandomPoint(a, b, 2);
            origin.add(new PersonLocation(doubles[0], doubles[1]));
        }

        for (int i = 0; i < 10; i++) {
            double[] doubles = generateRandomPoint(31.26598, 109.2654889, 2);
            origin.add(new PersonLocation(doubles[0], doubles[1]));
        }

        for (int i = 0; i < 3; i++) {
            double[] doubles = generateRandomPoint(37.26598, 104.2654889, 2);
            origin.add(new PersonLocation(doubles[0], doubles[1]));
        }
        detectGatheringPoint(origin);
    }

    private static final double EARTH_RADIUS = 6371000;

    /**
     * 生成一堆聚集点
     */
    private static double[] generateRandomPoint(double centerLat, double centerLon, double radiusInMeters) {
        Random random = new Random();
        double radiusInDegrees = radiusInMeters / EARTH_RADIUS;
        double angle = random.nextDouble() * Math.PI * 2;
        double distance = random.nextDouble() * radiusInDegrees;
        double randomLat = centerLat + Math.toDegrees(distance * Math.cos(angle));
        double randomLon = centerLon + Math.toDegrees(distance * Math.sin(angle)) /
                Math.cos(Math.toRadians(centerLat));
        return new double[]{randomLat, randomLon};
    }
}




